package org.versi.util.xml;

import java.io.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

/**
 * XML utility class with commonly used functions. Internally uses JAXP.
 * 
 * Note:
 *    JAXP uses a pluggable architecture, allowing you to swap b/n different
 *    XML parser and XSLT processor implementations. However:
 * 
 *    The default TransformerFactory from JDK 1.6.x has issues dealing with some
 *    XSLTs. Put Saxon's jar in classpath and the one from Saxon will be used
 *    instead.
 * 
 *    JAXP finds the relevant TransformerFactory implementation in this order*:
 * 
 *    1) Use the value of the javax.xml.transform.TransformerFactory system
 *       property if it exists.
 *    2) If JRE/lib/jaxp.properties exists, then look for a
 *       javax.xml.transform.TransformerFactory=ImplementationClass entry
 *       in that file.
 *    3) Use a JAR file service provider to look for a file called
 *       META-INF/services/javax.xml.transform.TransformerFactory in any JAR
 *       file on the CLASSPATH.
 *    4) Use the default TransformerFactory instance.
 * 
 *    * http://www.ling.helsinki.fi/kit/2004k/ctl257/JavaXSLT/Ch05.html
 * 
 * @author King Lung Chiu <kinglung.chiu -nospam- AT -nospam- gmail.com>
 */
public class XMLUtil
{
   /**
    * Transforms an XML document with a given XSLT, and returns the resultant
    * document as a byte array. Transformation is done in-memory, so no
    * temporary file is generated.<p>
    * Useful for chaining multiple in-memory transformations. For example:
    * <pre>
    *    byte[] resultA = XMLUtil.applyXSLT(
    *       getByteArray("input.xml"),
    *       getInputStream("firstXSLT.xsl"));
    *    byte[] resultB = XMLUtil.applyXSLT(
    *       resultA,
    *       getInputStream("secondXSLT.xsl"));
    * 
    *    ... keep going till last XSLT
    * 
    *    byte[] finalResult = XMLUtil.applyXSLT(
    *       resultN,
    *       getInputStream("finalXSLT.xsl"));
    * </pre>
    * 
    * @param inputXML
    *    Byte array containing the XML document to transform.
    * 
    * @param inputXSLT
    *    InputStream of the XSLT to apply.
    * 
    * @return
    *    The transformation result as a byte array.
    * 
    * @throws TransformerConfigurationException
    * @throws TransformerException 
    */
   public static byte[]
      applyXSLT(
         byte[]       inputXML,
         InputStream  inputXSLT)
      throws TransformerConfigurationException,
             TransformerException
   {
      ByteArrayOutputStream outputByteStream = new ByteArrayOutputStream();
      applyXSLT(
         new StreamSource(new ByteArrayInputStream(inputXML)),
         new StreamSource(inputXSLT),
         new StreamResult(outputByteStream));

      return outputByteStream.toByteArray();
   }
   

   /**
    * Transforms an XML document with a given XSLT, and writes the resultant
    * document to the given output stream.<p>
    * Useful as the last step in an in-memory multi-step transformation
    * scenario, where you want to save the final transformation result to
    * an external resource such as a file. For example:
    * <pre>
    *    // transform in memory for intermediate steps
    *    byte[] resultA = XMLUtil.applyXSLT(
    *       getByteArray("input.xml"),
    *       getInputStream("firstXSLT.xsl"));
    *    byte[] resultB = XMLUtil.applyXSLT(
    *       resultA,
    *       getInputStream("secondXSLT.xsl"));
    * 
    *    ... keep going till last XSLT
    * 
    *    // then save final transformation result to file
    *    XMLUtil.applyXSLT(
    *       resultN,
    *       getInputStream("finalXSLT.xsl"),
    *       new FileOutputStream("output.xml"));
    * </pre>
    * 
    * @param inputXML
    *    Byte array containing the XML document to transform.
    * 
    * @param inputXSLT
    *    InputStream of the XSLT to apply.
    * 
    * @param outputXML
    *    OutputStream for the transformation result.
    * 
    * @throws TransformerConfigurationException
    * @throws TransformerException 
    */
   public static void
      applyXSLT(
         byte[]       inputXML,
         InputStream  inputXSLT,
         OutputStream outputXML)
      throws TransformerConfigurationException,
             TransformerException
   {
      applyXSLT(
         new StreamSource(new ByteArrayInputStream(inputXML)),
         new StreamSource(inputXSLT),
         new StreamResult(outputXML));
   }
   

   /**
    * Transforms an XML document with a given XSLT, and writes the resultant
    * document to the given output stream.<p>
    * This is a simple IO streams-based wrapper to shield users from JAXP's
    * Transformer and TransformerFactory classes. Sample usage:
    * <pre>
    *    XMLUtil.applyXSLT(
    *       getInputStream("input.xml"),
    *       getInputStream("xsltToApply.xsl"),
    *       new FileOutputStream("output.xml"));
    * </pre>
    * 
    * @param inputXML
    *    InputStream of the XML document to transform.
    * 
    * @param inputXSLT
    *    InputStream of the XSLT to apply.
    * 
    * @param outputXML
    *    OutputStream for the transformation result.
    * 
    * @throws TransformerConfigurationException
    * @throws TransformerException 
    */
   public static void
      applyXSLT(
         InputStream inputXML,
         InputStream inputXSLT,
         OutputStream outputXML)
      throws TransformerConfigurationException,
             TransformerException
   {
      applyXSLT(
         new StreamSource(inputXML),
         new StreamSource(inputXSLT),
         new StreamResult(outputXML));
   }

   
   /**
    * Transforms an XML document with a given XSLT, and writes the resultant
    * document to the given StreamResult.<p>
    * This is a simple wrapper to shield users from JAXP's Transformer and
    * TransformerFactory classes. Sample usage:
    * <pre>
    *    XMLUtil.applyXSLT(
    *       new StreamSource(getInputStream("input.xml")),
    *       new StreamSource(getInputStream("xsltToApply.xsl")),
    *       new StreamResult(new FileOutputStream("output.xml")));
    * </pre>
    * 
    * @param inputXML
    *    StreamSource of the XML document to transform.
    * 
    * @param inputXSLT
    *    StreamSource of the XSLT to apply.
    * 
    * @param outputXML
    *    StreamResult to hold the transformation result.
    * 
    * @throws TransformerConfigurationException
    * @throws TransformerException 
    */
   public static void
      applyXSLT(
         StreamSource inputXML,
         StreamSource inputXSLT,
         StreamResult outputXML)
      throws TransformerConfigurationException,
             TransformerException
   {
      Transformer transformer = TransformerFactory
         .newInstance()
         .newTransformer(inputXSLT);

      transformer.transform(inputXML, outputXML);
   }

//
//   public static byte[]
//      applyXSLT(
//         StreamSource inputXML,
//         StreamSource inputXSLT)
//      throws TransformerConfigurationException,
//             TransformerException
//   {
//      ByteArrayOutputStream outputByteStream = new ByteArrayOutputStream();
//      applyXSLT(
//         inputXML,
//         inputXSLT,
//         new StreamResult(outputByteStream));
//
//      return outputByteStream.toByteArray();
//   }
//
//   public static byte[]
//      applyXSLT(
//         Reader inputXML,
//         Reader inputXSLT)
//      throws TransformerConfigurationException,
//             TransformerException
//   {
//      return applyXSLT(
//         new StreamSource(inputXML),
//         new StreamSource(inputXSLT));
//   }
//
//   public static byte[]
//      applyXSLT(
//         Reader inputXML,
//         StreamSource inputXSLT)
//      throws TransformerConfigurationException,
//             TransformerException
//   {
//      return applyXSLT(
//         new StreamSource(inputXML),
//         inputXSLT);
//   }
//
//   public static byte[]
//      applyXSLT(
//         StreamSource inputXML,
//         Reader inputXSLT)
//      throws TransformerConfigurationException,
//             TransformerException
//   {
//      return applyXSLT(
//         inputXML,
//         new StreamSource(inputXSLT));
//   }
}